/**
 * 
 */
package model;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.Timer;

import common.GridCoordinate;
import common.ItemFactory;
import common.MessageFactory;
import controller.Controller;

/**
 * @author Bill
 *
 *This is the high level "driver" for the game. It keeps
 *track of information such as the state of the game, and 
 *acts as a link between the rest of the model and the 
 *view/controller 
 */
public class GameDriver {
	
	//constants
	static final int NUM_LEVELS = 3;
	static final int WAVES_PER_LEVEL = 10;
	static final int START_POINTS = 200;
	static final int START_HEALTH = 100;
	static final int WAVE_BONUS_POINTS = 200;
	static final int LEVEL_BONUS_POINTS = 1500;
	
	//Possible game states
	static final State mainMenuState = new MainMenuState();
	static final State setupStageState = new SetupStageState();
	static final State attackStageState = new AttackStageState();
	static final State pauseMenuState = new PauseMenuState();
	static final State howToPlayState = new HowToPlayState();
	
	//current state of the game
	State currentState;
	
	//state and game update observers
	private ArrayList<StateObserver> stateObservers = new ArrayList<StateObserver>();
	
	private Map map;	
	private Store store;
	private int currentLevel = 1;
	private int currentWave = 0;	
	private Player player;
	
	public GameDriver(){
		currentState = mainMenuState;
		player = new Player(START_POINTS, START_HEALTH); //initialize a player with default start values
		map = new Map(currentLevel, player); //initialize with the first level map
		store = new Store( player , map);
	}
	
	public void advanceState(){
		currentState.advanceState(this);
	}
	
	public void returnToMainMenu(){
		currentState.returnToMainMenu(this);
	}
	
	public void pauseGame(){
		currentState.pauseButtonPressed(this);
	}
	
	/**
	 * Updates the game to the next level. Returns false if there are no more levels left.
	 */
	public boolean advanceToNextLevel(){
		if(currentLevel < NUM_LEVELS){
			int levelBonusPoints = LEVEL_BONUS_POINTS*currentLevel;
			currentLevel++;
			Controller.createNewMapView(this);
			Controller.displayHighPriorityMessage("<html>Advanced to level: "+currentLevel+"<br>Awarded $"+levelBonusPoints+"</html>",2000);
			this.player.addPoints(levelBonusPoints);
			this.map = new Map(currentLevel, player);
			Controller.updateView();
			return true;
		} else {
			Controller.displayHighPriorityMessage("YOU WIN THE GAME!",3000);
			Timer winTimer = new Timer(4000, new ActionListener(){
				@Override
				public void actionPerformed(ActionEvent arg0) {
					currentLevel = 1;
					Controller.returnToMainMenu();
				}
			});
			Controller.stopTimer();
			winTimer.setRepeats(false);
			winTimer.start();
			return false;
		}
	}
	
	/**
	 * Performs an action depending on which tile was clicked. If the action
	 * cannot be performed (for example if you try to add a weapon to a tile which
	 * already has a weapon on it) then this returns false
	 * 
	 * @param tile - the tile which was clicked
	 * @return true if the action can be performed, false otherwise
	 */
	public boolean tileClicked(GridCoordinate tile){
		if(currentState == setupStageState){
			if(store.isItemSelected()){
				Item item = ItemFactory.createItemFromImageName(store.getSelectedItem());
				if( item instanceof AutomaticWeapon || item instanceof DefensiveItem ){
					boolean success = map.layItem(store.getSelectedItem(), tile);
					if(success){
						store.deselectAndBuyItem();
						System.out.println("Bought and have points = " + player.getPoints());
					}
					return success;
				}
				else 
					return false;
			}
			else if(Map.getMapGrid().get(tile.getY()).get(tile.getX()).hasItem()){
				Item curItem = Map.getMapGrid().get(tile.getY()).get(tile.getX()).getItem();
				System.out.println("Rotating " + curItem.getName());
				AutomaticWeapon autoWeapon = (AutomaticWeapon) curItem;
				if(autoWeapon != null){
					autoWeapon.rotateCCW();
				}
				Controller.updateView();
				return true;
			}
			else return false;
		}
		else if(currentState == attackStageState){
			if(player.getCargo().isWeaponSelected()){
				ManualWeapon manWeapon = player.getCargo().getManualWeapon(player.getCargo().getSelectedWeapon());
				//attack area on map
				map.fireManualWeapon(manWeapon, tile);
				Controller.updateView();
				return true;
			}
			return false;
		}
		else return false;
	}
	
	public void addStateObserver(StateObserver observer){
		stateObservers.add(observer);
	}	
	public void removeStateObserver(StateObserver observer){
		stateObservers.remove(observer);
	}
	
	/*****************************************
	 * Getters and setters
	 *****************************************/
	public static int getNumLevels() {
		return NUM_LEVELS;
	}

	public static int getWavesPerLevel() {
		return WAVES_PER_LEVEL;
	}

	public static int getStartPoints() {
		return START_POINTS;
	}

	public static int getStartHealth() {
		return START_HEALTH;
	}

	public Map getMap() {
		return map;
	}

	public void setMap(Map map) {

		this.map = map;
	}
	
	public Store getStore() {
		return store;
	}

	public void setStore(Store store) {

		this.store = store;
	}
	
	public String getCurrentStateName(){
		return currentState.getStateName();
	}
	
	//sets the current state and notifies all state observers
	void setCurrentState(State s){
		currentState = s;
		for(int i = 0; i < stateObservers.size(); i++){
			stateObservers.get(i).gameStateChanged(s);
		}
	}
	
	public State getCurrentState() {
		return currentState;
	}

	public Player getPlayer() {
		return player;
	}

	public void setPlayer(Player player) {
		this.player = player;
	}

	public int getCurrentLevel(){
		return currentLevel;
	}
	
	public void setCurrentLevel(int level){
		currentLevel = level;
	}
	
	public void incrementWave(){
		setCurrentWave(currentWave+1);
		player.addPoints(WAVE_BONUS_POINTS/2 + WAVE_BONUS_POINTS*currentLevel/2);
	}
	
	public int getCurrentWave(){
		return this.currentWave;
	}
	
	public void setCurrentWave(int wave){
		this.currentWave = wave;
		map.setCurrentWave(WaveFactory.getWave(currentWave, currentLevel));
	}
}
